﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace Moonlite.Modules.Editors.ImageViewer.Dds
{
    internal static class DdsDxt
    {
        private static void DecodeColor(ushort color, out byte red, out byte green, out byte blue)
        {
            red = (byte)(((color >> 11) & 0x1f) << 3);
            green = (byte)(((color >> 5) & 0x3f) << 2);
            blue = (byte)((color & 0x1f) << 3);
        }

        private static byte CalcC2(byte c0, byte c1)
        {
            return (byte)(2 * c0 / 3 + c1 / 3);
        }

        private static byte CalcC2A(byte c0, byte c1)
        {
            return (byte)(c0 / 2 + c1 / 2);
        }

        private static byte CalcC3(byte c0, byte c1)
        {
            return (byte)(c0 / 3 + 2 * c1 / 3);
        }

        private static Color[] FillColors(ushort c0, ushort c1, uint table)
        {
            return FillColors(c0, c1, table, false);
        }

        private static Color[] FillColors(ushort c0, ushort c1, uint table, bool dxt1a)
        {
            byte[] r = new byte[4];
            byte[] g = new byte[4];
            byte[] b = new byte[4];
            byte[] a = new byte[4];
            for (int i = 0; i < 4; i++)
            {
                a[i] = 255;
            }

            DecodeColor(c0, out r[0], out g[0], out b[0]);
            DecodeColor(c1, out r[1], out g[1], out b[1]);
            if (!dxt1a)
            {
                r[2] = CalcC2(r[0], r[1]);
                g[2] = CalcC2(g[0], g[1]);
                b[2] = CalcC2(b[0], b[1]);
                r[3] = CalcC3(r[0], r[1]);
                g[3] = CalcC3(g[0], g[1]);
                b[3] = CalcC3(b[0], b[1]);
            }
            else
            {
                r[2] = CalcC2A(r[0], r[1]);
                g[2] = CalcC2A(g[0], g[1]);
                b[2] = CalcC2A(b[0], b[1]);
                r[3] = 0;
                g[3] = 0;
                b[3] = 0;
                a[3] = 0;
            }

            Color[] result = new Color[16];
            for (int k = 0; k < 4; k++)
            {
                for (int l = 0; l < 4; l++)
                {
                    uint index = table & 0x0003;
                    table = table >> 2;
                    int red = (int)r[index];
                    int green = (int)g[index];
                    int blue = (int)b[index];
                    int alpha = (int)a[index];
                    result[k * 4 + l] = Color.FromArgb(alpha, red, green, blue);
                }
            }

            return result;
        }

        private static void SetAlphaDxt5(Color[] rgbArray, ulong alphas)
        {
            byte[] a = new byte[8];
            a[0] = (byte)(alphas & 0xff);
            a[1] = (byte)((alphas >> 8) & 0xff);
            if (a[0] > a[1])
            {
                a[2] = (byte)((6 * a[0] + 1 * a[1]) / 7);
                a[3] = (byte)((5 * a[0] + 2 * a[1]) / 7);
                a[4] = (byte)((4 * a[0] + 3 * a[1]) / 7);
                a[5] = (byte)((3 * a[0] + 4 * a[1]) / 7);
                a[6] = (byte)((2 * a[0] + 5 * a[1]) / 7);
                a[7] = (byte)((1 * a[0] + 6 * a[1]) / 7);
            }
            else
            {
                a[2] = (byte)((4 * a[0] + 1 * a[1]) / 5);
                a[3] = (byte)((3 * a[0] + 2 * a[1]) / 5);
                a[4] = (byte)((2 * a[0] + 3 * a[1]) / 5);
                a[5] = (byte)((1 * a[0] + 4 * a[1]) / 5);
                a[6] = 0;
                a[7] = 255;
            }

            alphas >>= 16;
            for (int i = 0; i < 16; i++)
            {
                byte index = (byte)(alphas & 0x07);
                rgbArray[i] = Color.FromArgb((int)a[index], rgbArray[i]);
                alphas = alphas >> 3;
            }
        }

        public static Bitmap LoadDxt1(BinaryReader stream, int width, int height)
        {
            Bitmap image = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            for (uint i = 0; i < height / 4; i++)
            {
                for (uint j = 0; j < width / 4; j++)
                {
                    ushort c0 = stream.ReadUInt16();
                    ushort c1 = stream.ReadUInt16();
                    uint table = stream.ReadUInt32();
                    Color[] colorArray = null;
                    if (c0 > c1)
                    {
                        colorArray = FillColors(c0, c1, table, false);
                    }
                    else
                    {
                        colorArray = FillColors(c0, c1, table, true);
                    }

                    for (int k = 0; k < 4; k++)
                    {
                        for (int l = 0; l < 4; l++)
                        {
                            image.SetPixel((int)(j * 4 + l), (int)(i * 4 + k), colorArray[k * 4 + l]);
                        }
                    }
                }
            }

            return image;
        }

        public static Bitmap LoadDxt5(BinaryReader stream, int width, int height)
        {
            Bitmap image = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            for (uint i = 0; i < height / 4; i++)
            {
                for (uint j = 0; j < width / 4; j++)
                {
                    ulong alpha = stream.ReadUInt64();
                    ushort c0 = stream.ReadUInt16();
                    ushort c1 = stream.ReadUInt16();
                    uint table = stream.ReadUInt32();
                    Color[] colorArray = FillColors(c0, c1, table);
                    SetAlphaDxt5(colorArray, alpha);

                    for (int k = 0; k < 4; k++)
                    {
                        for (int l = 0; l < 4; l++)
                        {
                            image.SetPixel((int)(j * 4 + l), (int)(i * 4 + k), colorArray[k * 4 + l]);
                        }
                    }
                }
            }

            return image;
        }
    }
}
